package com.vmj;

import com.vmj.classfile.Classpath;
import com.vmj.heap.*;
import com.vmj.heap.ClassLoader;
import com.vmj.instructions.Interpreter;
import com.vmj.rtda.Frame;
import com.vmj.rtda.Thread;
import org.apache.commons.cli.*;

/**
 * Created by yangshoujun on 2017/8/5 下午6:31.
 */
public class Bootstrap {

    public static void main(String[] args) throws Throwable {

        CommandLineParser parser = new DefaultParser();
        Options options = new Options();
        options.addOption(Option.builder("h").longOpt("help").numberOfArgs(0).desc("show help").build());
        options.addOption(Option.builder("Xjre").longOpt("Xjre").numberOfArgs(1).desc("specific the jre path").build());
        options.addOption(Option.builder("cp").longOpt("cp").numberOfArgs(1).desc("specific the classpath").build());
        options.addOption(Option.builder("classpath").longOpt("classpath").numberOfArgs(1).desc("specific the classpath").build());
        try {
            CommandLine command = parser.parse(options, args);
            Command cmd = new Command();
            int count = 0;
            if(command.hasOption("Xjre")) {
                cmd.setJavaHome(command.getOptionValue("Xjre"));
                count++;
            }
            if(command.hasOption("classpath")) {
                cmd.setClasspath(command.getOptionValue("classpath"));
                count++;
            }
            if(command.hasOption("cp")) {
                cmd.setClasspath(command.getOptionValue("cp"));
                count++;
            }
            if(count > 0 && args.length > count) {
                int argsCount = args.length - count;
                if(argsCount < 1) {
                    throw new RuntimeException("argument count must not less than one");
                }
                String[] arguments = new String[argsCount];
                System.arraycopy(args, count, arguments, 0, args.length - count);
                cmd.setArgs(arguments);
            }
            checkSetArgs(cmd);
            startVm(cmd);
        } catch (ParseException e) {
            e.printStackTrace();
            System.exit(-1);
        }

    }

    static boolean startVm(Command cmd) {

        Classpath classpath = new Classpath(cmd);
        String mainClass = cmd.getArgs()[0];

        String[] args = new String[cmd.getArgs().length - 1];
        System.arraycopy(cmd.getArgs(), 1, args, 0, cmd.getArgs().length - 1);

        ClassLoader classLoader = new ClassLoader(classpath);
        VMClass clazz = classLoader.loadClass(mainClass);
        Method main = clazz.getMainMethod();
        if(main == null) {
            throw new RuntimeException("not find main method in class: " + mainClass);
        }

        Thread thread = new Thread();
        VMClass vmClass = classLoader.loadClass("sun/misc/VM");
        VMClass.initClass(thread, vmClass);
        VMObject mainArgs = createArgsArray(classLoader, args);
        Frame frame = new Frame(thread, main);
        frame.getLocalVars().setRef(0, mainArgs);
        thread.pushFrame(frame);
        Interpreter interpreter = new Interpreter();
        interpreter.loop(thread);
        return true;
    }

    public static void checkSetArgs(Command cmd) {
        if(cmd.getJavaHome() == null || "".equals(cmd.getJavaHome().trim())) {
            String jhEnv = System.getenv("JAVA_HOME");
            if(jhEnv == null) {
                throw  new RuntimeException("JAVA_HOME not exists.");
            }
            cmd.setJavaHome(jhEnv);
        }

        if(cmd.getClasspath() == null || "".equals(cmd.getClasspath())
                || ".".equals(cmd.getClasspath())) {
            cmd.setClasspath(System.getProperty("user.dir"));
        }
    }

    private static VMObject createArgsArray(ClassLoader classLoader, String[] args) {
        VMClass stringClass = classLoader.loadClass("java/lang/String");
        VMObject argsObj = VMObject.newArray(stringClass.getArrayClass(), args.length);
        VMObject[] refs = (VMObject[]) argsObj.getData();
        for(int i = 0;i < args.length;i++) {
            refs[i] = StringPool.getStringObject(classLoader, args[i]);
        }

        return argsObj;
    }
}
